﻿////////////////////////////////////////////////////////////////////////////////
// Source Copyright (C) J.Fan
// @author      J.Fan
// @date        2013-01-19
// @revision    Revision in Version Control System
// @remark      
////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;

namespace JsonConverter.Converter
{
    public class ConverterProvider
    {
        public static ConverterProvider Default { get; private set; }

        Dictionary<Type, IPrimitiveConverter> primitiveConverters;
        Dictionary<Type, IObjectConverter> objectConverters;

        /// <summary>
        /// get/set a converter to converter null value.
        /// </summary>
        public IConverter NullConverter
        {
            get
            {
                if (nullConverter == null)
                {
                    nullConverter = Converter.NullConverter.Instance;
                }
                return nullConverter;
            }
            set
            {
                nullConverter = value ?? Converter.NullConverter.Instance;
            }
        }
        private IConverter nullConverter;

        protected bool HasPrimitiveConverter
        {
            get { return !(primitiveConverters == null || primitiveConverters.Count == 0); }
        }

        protected bool HasObjectConverter
        {
            get { return !(objectConverters == null || objectConverters.Count == 0); }
        }

        protected Dictionary<Type, IPrimitiveConverter> PrimitiveConverters
        {
            get
            {
                if (primitiveConverters == null)
                {
                    primitiveConverters = new Dictionary<Type, IPrimitiveConverter>();
                }
                return primitiveConverters;
            }
        }

        protected Dictionary<Type, IObjectConverter> ObjectConverters
        {
            get
            {
                if (objectConverters == null)
                {
                    objectConverters = new Dictionary<Type, IObjectConverter>();
                }
                return objectConverters;
            }
        }

        /// <summary>
        /// Initialize ConverterProvider.Default
        /// </summary>
        static ConverterProvider()
        {
            Default = new ConverterProvider();
        }

        /// <summary>
        /// Hidden constructor. It's just for Default
        /// </summary>
        /// <param name="isInternal"></param>
        public ConverterProvider() : this(null) { }

        /// <summary>
        /// Clone converters from prototype
        /// </summary>
        /// <param name="prototype"></param>
        public ConverterProvider(ConverterProvider prototype)
        {
            if (prototype == null)
            {
                return;
            }

            NullConverter = prototype.NullConverter;

            if (prototype.primitiveConverters != null)
            {
                primitiveConverters
                    = new Dictionary<Type, IPrimitiveConverter>(prototype.primitiveConverters);
            }

            if (prototype.objectConverters != null)
            {
                objectConverters
                    = new Dictionary<Type, IObjectConverter>(prototype.objectConverters);
            }
        }

        /// <summary>
        /// Get a converter by type.
        /// If the type is registered both a PrimitivieConverter and a ObjectConverter,
        /// the PrimitiveConverter is returned.
        /// If the type is not registered, a CustomObjectConverter is returned.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual IConverter this[Type type]
        {
            get
            {
                return (IConverter) GetPrimitiveConverter(type)
                    ?? GetObjectConverter(type)
                    ?? InternalConverterProvider.Instance.CustomObjectConverter;
            }
        }

        #region PrimitiveConverters
        /// <summary>
        /// Register the converter to the type
        /// </summary>
        /// <param name="converter"></param>
        /// <param name="type"></param>
        public virtual void Register(IPrimitiveConverter converter, Type type)
        {
            if (converter == null) { throw new ArgumentNullException("converter"); }
            PrimitiveConverters[type] = converter;
        }

        /// <summary>
        /// Register the converter to the types
        /// </summary>
        /// <param name="converter"></param>
        /// <param name="types"></param>
        public virtual void Register(IPrimitiveConverter converter, params Type[] types)
        {
            if (converter == null) { throw new ArgumentNullException("converter"); }
            if (types == null || types.Length == 0) { return; }

            Dictionary<Type, IPrimitiveConverter> converters = PrimitiveConverters;
            foreach (Type type in types)
            {
                converters[type] = converter;
            }
        }

        /// <summary>
        /// Unregister the type's converter.
        /// </summary>
        /// <param name="type"></param>
        public virtual void UnregisterPrimitiveConverter(Type type)
        {
            if (HasPrimitiveConverter)
            {
                primitiveConverters.Remove(type);
            }
        }

        /// <summary>
        /// Unregister the types' converter.
        /// </summary>
        /// <param name="types"></param>
        public virtual void UnregisterPrimitiveConverter(params Type[] types)
        {
            if (types == null || types.Length == 0 || !HasPrimitiveConverter)
            {
                return;
            }

            foreach (Type type in types)
            {
                primitiveConverters.Remove(type);
            }
        }

        /// <summary>
        /// Get the converter by type.
        /// If the converter is not register in current provider,
        /// try to find it in internal predefined provider.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual IPrimitiveConverter GetPrimitiveConverter(Type type)
        {
            IPrimitiveConverter converter;
            if (HasPrimitiveConverter
                && primitiveConverters.TryGetValue(type, out converter))
            {
                return converter;
            }
            else
            {
                return InternalConverterProvider.Instance.GetPrimitiveConverter(type);
            }
        }
        #endregion

        #region ObjectConverters
        /// <summary>
        /// Register the converter to the type.
        /// </summary>
        /// <param name="converter"></param>
        /// <param name="type"></param>
        public virtual void Register(IObjectConverter converter, Type type)
        {
            if (converter == null) { throw new ArgumentNullException("converter"); }
            ObjectConverters[type] = converter;
        }

        /// <summary>
        /// Register the converter to the types.
        /// </summary>
        /// <param name="converter"></param>
        /// <param name="types"></param>
        public virtual void Register(IObjectConverter converter, params Type[] types)
        {
            if (converter == null) { throw new ArgumentNullException("converter"); }
            if (types == null || types.Length == 0) { return; }

            Dictionary<Type, IObjectConverter> converters = ObjectConverters;
            foreach (Type type in types)
            {
                converters[type] = converter;
            }
        }

        /// <summary>
        /// Unregister the type's converter.
        /// </summary>
        /// <param name="type"></param>
        public virtual void UnregisterObjectConverter(Type type)
        {
            if (HasPrimitiveConverter)
            {
                objectConverters.Remove(type);
            }
        }

        /// <summary>
        /// Unregister the types' converter.
        /// </summary>
        /// <param name="types"></param>
        public virtual void UnregisterObjectConverter(params Type[] types)
        {
            if (types == null || types.Length == 0 || !HasObjectConverter)
            {
                return;
            }

            foreach (Type type in types)
            {
                objectConverters.Remove(type);
            }
        }

        /// <summary>
        /// Get the converter by type.
        /// If the converter is not register in current provider,
        /// try to find it in internal predefined provider.
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public virtual IObjectConverter GetObjectConverter(Type type)
        {
            IObjectConverter converter;
            if (HasObjectConverter
                && objectConverters.TryGetValue(type, out converter))
            {
                return converter;
            }
            else
            {
                return InternalConverterProvider.Instance.GetObjectConverter(type);
            }
        }
        #endregion
    }
}
